package smartgrid.agents.prosumer;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import smartgrid.agents.AbstractAgent;
import smartgrid.conf.ProsumerConf;
import smartgrid.modules.normative.sanction.SanctionAbstract;
import smartgrid.modules.trade.Contract;
import smartgrid.modules.trade.Demand;
import smartgrid.modules.trade.Offer;
import smartgrid.util.random.RandomUtil;

public class ProsumerAbstractAgent extends AbstractAgent implements
		IProsumerAgent {
	
	@SuppressWarnings("unused")
	private static final Logger										logger	= LoggerFactory
																														.getLogger(ProsumerAbstractAgent.class);
	
	protected double															wealth;
	
	protected Map<Integer, List<IProsumerAgent>>	neighbors;
	
	protected Map<Integer, Map<Integer, Double>>	distances;
	
	protected Map<Integer, IProsumerAgent>				prosumers;
	
	protected double															selfInterest;
	
	protected double															consumeEstimation;
	
	protected double															generateEstimation;
	
	protected double															consumeReal;
	
	protected double															generateReal;
	
	protected double															consumeAccuracy;
	
	protected double															generateAccuracy;
	
	protected double															minConsume;
	
	protected double															maxConsume;
	
	protected double															minGenerate;
	
	protected double															maxGenerate;
	
	protected double															consumeVar;
	
	protected double															generateVar;
	
	protected double															sellingPrice;
	
	protected Contract														contract;
	
	protected Offer																offer;
	
	protected Demand															demand;
	
	protected double															providerBuyEnergy;
	
	protected double															providerAmountPaid;
	
	protected double															providerSellEnergy;
	
	protected double															providerAmountReceived;
	
	
	/**
	 * Constructor
	 * 
	 * @param id
	 *          Prosumer identification
	 * @param conf
	 *          Prosumer configuration
	 * @return none
	 */
	public ProsumerAbstractAgent(Integer id, ProsumerConf conf) {
		super(id);
		
		this.wealth = 0.0;
		this.wealth = 0.0;
		this.neighbors = new HashMap<Integer, List<IProsumerAgent>>();
		this.distances = new HashMap<Integer, Map<Integer, Double>>();
		this.prosumers = new HashMap<Integer, IProsumerAgent>();
		
		this.selfInterest = conf.getSelfInterest();
		
		this.sellingPrice = conf.getMinPrice()
				+ ((conf.getMaxPrice() - conf.getMinPrice()) * RandomUtil.nextDouble());
		
		this.minConsume = conf.getMinConsume();
		this.maxConsume = conf.getMaxConsume();
		this.minGenerate = conf.getMinGenerate();
		this.maxGenerate = conf.getMaxGenerate();
		this.consumeVar = conf.getConsumeVariation();
		this.generateVar = conf.getGenerateVariation();
		
		this.consumeAccuracy = 1.0;
		this.generateAccuracy = 1.0;
		
		this.providerBuyEnergy = 0.0;
		this.providerAmountPaid = 0.0;
		this.providerSellEnergy = 0.0;
		this.providerAmountReceived = 0.0;
	}
	
	
	/**
	 * Get calculated price per unit
	 * 
	 * @param none
	 * @return Price
	 */
	public double getPrice() {
		return this.sellingPrice;
	}
	
	
	/**
	 * Get accumulated wealth
	 * 
	 * @param none
	 * @return Wealth
	 */
	public double getWealth() {
		return this.wealth;
	}
	
	
	@Override
	public void setNeighbors(Map<Integer, List<IProsumerAgent>> neighbors) {
		this.neighbors = neighbors;
	}
	
	
	@Override
	public void setDistance(Map<Integer, Map<Integer, Double>> distances) {
		this.distances = distances;
	}
	
	
	@Override
	public void setProsumers(Map<Integer, IProsumerAgent> prosumers) {
		this.prosumers = prosumers;
	}
	
	
	@Override
	public void initSim() {
	}
	
	
	@Override
	public void initTimestep(int timestep) {
		this.timestep = timestep;
		this.consumeEstimation = 0.0;
		this.generateEstimation = 0.0;
		this.contract = null;
		this.offer = null;
		this.demand = null;
		this.consumeReal = 0.0;
		this.generateReal = 0.0;
		this.providerBuyEnergy = 0.0;
		this.providerAmountPaid = 0.0;
		this.providerSellEnergy = 0.0;
		this.providerAmountReceived = 0.0;
	}
	
	
	@Override
	public void estimationConGen() {
		this.consumeEstimation = Math.max(0, this.minConsume
				+ ((this.maxConsume - this.minConsume) * RandomUtil.nextDouble()));
		
		this.generateEstimation = Math.max(0, this.minGenerate
				+ ((this.maxGenerate - this.minGenerate) * RandomUtil.nextDouble()));
	}
	
	
	@Override
	public double getConsumeEstimation() {
		return this.consumeEstimation;
	}
	
	
	@Override
	public double getGenerateEstimation() {
		return this.generateEstimation;
	}
	
	
	@Override
	public boolean hasExcessEnergyEstimation() {
		if (this.generateEstimation > this.consumeEstimation) {
			return true;
		}
		
		return false;
	}
	
	
	@Override
	public Offer getOffer() {
		return this.offer;
	}
	
	
	@Override
	public Demand getDemand(Map<Integer, Offer> offers) {
		return this.demand;
	}
	
	
	@Override
	public Contract getContract() {
		return this.contract;
	}
	
	
	@Override
	public void setContract(Contract contract) {
		this.contract = contract;
	}
	
	
	@Override
	public void realConGen() {
		
		this.consumeReal = Math.max(
				0,
				this.consumeEstimation
						* RandomUtil.nextDoubleFromTo(1 - this.consumeVar,
								1 + this.consumeVar));
		
		this.generateReal = Math.max(
				0,
				this.generateEstimation
						* RandomUtil.nextDoubleFromTo(1 - this.generateVar,
								1 + this.generateVar));
		
		this.consumeAccuracy = (this.consumeAccuracy + (1 - (Math
				.abs(this.consumeEstimation - this.consumeReal) / Math.max(
				this.consumeEstimation, this.consumeReal)))) / 2;
		
		this.generateAccuracy = (this.generateAccuracy + (1 - (Math
				.abs(this.generateEstimation - this.generateReal) / Math.max(
				this.generateEstimation, this.generateReal)))) / 2;
	}
	
	
	@Override
	public double getConsumeReal() {
		return this.consumeReal;
	}
	
	
	@Override
	public double getGenerateReal() {
		return this.generateReal;
	}
	
	
	@Override
	public boolean hasExcessEnergyReal() {
		if (this.generateReal > this.consumeReal) {
			return true;
		}
		
		return false;
	}
	
	
	@Override
	public void updateContract() {
		if ((this.contract != null) && (this.contract.getSupplier() == this.id)) {
			
			IProsumerAgent consumer = this.prosumers.get(this.contract.getBuyer());
			
			double qtyExcessConsumed = consumer.getConsumeReal()
					- consumer.getGenerateReal();
			
			double qtyExcessGenerated = Math.max(0,
					(this.generateReal - this.consumeReal));
			
			// Wanted to buy
			double qtyConsumed;
			
			// Supplied
			double qtySupplied;
			if (qtyExcessConsumed > 0) {
				qtyConsumed = Math.min(this.contract.getQtyDemanded(),
						qtyExcessConsumed);
				
				qtySupplied = Math.min(qtyConsumed,
						Math.min(this.contract.getQtyOffered(), qtyExcessGenerated));
			} else {
				qtyConsumed = 0.0;
				qtySupplied = 0.0;
			}
			
			this.contract.setQtySupplied(qtySupplied);
			this.contract.setQtyConsumed(qtyConsumed);
			
			this.contract.setTotalPrice();
		}
	}
	
	
	@Override
	public void updateWealth() {
		if (this.contract != null) {
			
			this.contract.setTotalPrice();
			
			if (this.contract.getSupplier() == this.id) {
				this.wealth += this.contract.getTotalPrice();
			} else if (this.contract.getBuyer() == this.id) {
				this.wealth -= this.contract.getTotalPrice();
			}
		}
	}
	
	
	@Override
	public double energyBalance() {
		double balance = 0.0;
		
		if (this.contract != null) {
			balance = this.generateReal - this.consumeReal;
			if (this.contract.getSupplier() == this.id) {
				balance -= this.contract.getQtyTransacted();
			} else if (this.contract.getBuyer() == this.id) {
				balance += this.contract.getQtyTransacted();
			}
		}
		
		return balance;
	}
	
	
	@Override
	public void providerTrading(boolean buy, double qtyEnergy, double totalPrice) {
		if (buy) {
			this.wealth -= totalPrice;
			this.providerBuyEnergy = qtyEnergy;
			this.providerAmountPaid = totalPrice;
		} else {
			this.wealth += totalPrice;
			this.providerSellEnergy = qtyEnergy;
			this.providerAmountReceived = totalPrice;
		}
	}
	
	
	@Override
	public void evaluateContract() {
	}
	
	
	@Override
	public List<SanctionAbstract> decideSanction() {
		return new ArrayList<SanctionAbstract>();
	}
	
	
	@Override
	public void handleSanction(SanctionAbstract sanction) {
	}
	
	
	@Override
	public void updateStrategy(
			Map<Integer, List<SanctionAbstract>> sanctionsSender,
			Map<Integer, List<SanctionAbstract>> sanctionsReceiver) {
	}
	
	
	@Override
	public void generateOutput() {
	}
	
	
	@Override
	public String toString() {
		String str = new String();
		
		str += this.id;
		
		return str;
	}
}